/*
Copyright 2010,2011 Matt Van Der Westhuizen

This file is part of CGLL.

CGLL is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

CGLL is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with CGLL.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.chaoticengine.cgll.console;

import java.util.ArrayList;
import org.newdawn.slick.Color;
import org.newdawn.slick.Font;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.geom.Rectangle;

/**
 * A console that can be used to display a text console with coloured
 * text and which provides convenience methods for text input / output.
 *
 * @author Matt v.d. Westhuizen
 */
public class Console {
    public static final String SUPPORTED_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`~!@#$%^&*()-_+=[]{};:'\"<>,./?|\\";
    protected Rectangle dimensions = null;
    protected Font font = null;
    protected Color fgColor = Color.lightGray;
    protected Color bgColor = Color.black;
    protected int charHeight = 0;
    protected int charWidth = 0;
    protected int rows = 0;
    protected int cols = 0;
    protected ArrayList<String> lines = new ArrayList<String>();
    protected ArrayList<Boolean> linesDirty = new ArrayList<Boolean>();
    protected ArrayList<ArrayList<LinePart>> linesParts =
            new ArrayList<ArrayList<LinePart>>();

    public Console(Rectangle dimensions, Font fnt, Color fgColor, Color bgColor) {
        this.dimensions = dimensions;
        this.font = fnt;
        this.fgColor = fgColor;
        this.bgColor = bgColor;

        // Get maximum character height
        charHeight = font.getHeight(SUPPORTED_CHARS);

        // Get maximum character width
        for (int i = 0; i < SUPPORTED_CHARS.length(); i++) {
            int w = font.getWidth(SUPPORTED_CHARS.charAt(i) + "");
            
            if (w > charWidth) {
                charWidth = w;
            }
        }

        // Calculate rows & columns
        rows = ((int) dimensions.getHeight()) / charHeight;
        cols = ((int) dimensions.getWidth()) / charWidth;

        // Add empty lines
        /*
        for (int i = 0; i < rows; i++) {
            lines.add("");
            linesDirty.add(Boolean.TRUE);
        }
         * 
         */

        // Add empty rendering data structure
        for (int i = 0; i < rows; i++) {
            linesParts.add(new ArrayList<LinePart>());
        }
    }

    public void clear() {
        lines.clear();
        linesDirty.clear();

        for (int i = 0; i < linesParts.size(); i++) {
            linesParts.get(i).clear();
        }
    }

    public void println(String line) {
        lines.add(line);
        linesDirty.add(Boolean.TRUE);

        if (lines.size() >= rows) {
            lines.remove(0);
            linesDirty.remove(0);

            for (int i = 0; i < linesDirty.size(); i++) {
                linesDirty.set(i, Boolean.TRUE);
            }
        }
    }

    public void update(GameContainer gc, StateBasedGame sbg) {
        // Check for and recreate renderind data for dirty lines
        for (int i = 0; i < linesDirty.size(); i++) {
            boolean dirty = linesDirty.get(i).booleanValue();

            if (dirty) {
                updateLineRenderInfo(i);

                linesDirty.set(i, Boolean.FALSE);
            }
        }
    }

    public void render(GameContainer gc, StateBasedGame sbg, Graphics gr) {
        for (int i = 0; i < linesParts.size(); i++) {
            ArrayList<LinePart> parts = linesParts.get(i);

            for (int j = 0; j < parts.size(); j++) {
                parts.get(j).render(gc, sbg, gr);
            }
        }
    }

    private void updateLineRenderInfo(int lineNo) {
        ArrayList<LinePart> parts = linesParts.get(lineNo);
        parts.clear();

        String line = lines.get(lineNo);
        do {
            int pos = line.indexOf('&');
            
            if (pos > 0) {
                //// Formatting ahead
                // Get text
                String text = line.substring(0, pos);
                line = line.substring(pos);

                // Calculate position
                int width = 0;
                for (int i = 0; i < parts.size(); i++) {
                    width += parts.get(i).getWidth();
                }
                Rectangle partPos = new Rectangle(width, lineNo * charHeight,
                        font.getWidth(text), charHeight);
                LinePart part = new LinePart(partPos, text, fgColor, bgColor);
                parts.add(part);
            } else if (pos == 0) {
                //// Formatting now
                // Get formatting
                Color fmtBg = bgColor;
                Color fmtFg = fgColor;
                
                int semiPos = line.indexOf(";");
                if (semiPos > 0) { // Extract formatting
                    String format = line.substring(1, semiPos);
                    line = line.substring(semiPos + 1);

                    fmtFg = Color.decode(format);
                } // ELSE: Not formatting after all...

                // Get text
                String text = "";
                pos = line.indexOf("&;");
                if (pos < 0) {
                    text = line;
                    line = "";
                } else {
                    text = line.substring(0, pos);
                    line = line.substring(pos + 2);
                }

                // Calculate position
                int width = 0;
                for (int i = 0; i < parts.size(); i++) {
                    width += parts.get(i).getWidth();
                }
                Rectangle partPos = new Rectangle(width, lineNo * charHeight,
                        font.getWidth(text), charHeight);
                LinePart part = new LinePart(partPos, text, fmtFg, fmtBg);
                parts.add(part);
            } else {
                // No formatting remains
                if (line.length() > 0) {
                    // Get text
                    String text = line;
                    line = "";

                    // Calculate position
                    int width = 0;
                    for (int i = 0; i < parts.size(); i++) {
                        width += parts.get(i).getWidth();
                    }
                    Rectangle partPos = new Rectangle(width, lineNo * charHeight,
                            font.getWidth(text), charHeight);
                    LinePart part = new LinePart(partPos, text, fgColor, bgColor);
                    parts.add(part);
                }
            }
        } while (line.length() > 0);
    }

    public class LinePart {
        protected Rectangle position = null;
        protected String text = "";
        protected Color fg = Color.lightGray;
        protected Color bg = Color.black;

        public LinePart(Rectangle pos, String text, Color fg, Color bg) {
            this.position = pos;
            this.text = text;
            this.fg = fg;
            this.bg = bg;
        }

        public int getWidth() {
            return((int) position.getWidth());
        }

        public void render(GameContainer gc, StateBasedGame sbg, Graphics gr) {
            // Translation & rotation
            gr.translate(position.getX(), position.getY());

            // Rendering
            Color oCol = gr.getColor();
            Font oFont = gr.getFont();

            //// Clear the position
            gr.setColor(bg);
            gr.fillRect(0, 0, position.getWidth(), position.getHeight());

            //// Draw the new text
            gr.setFont(font);
            gr.setColor(fg);
            gr.drawString(text, 0.0f, 0.0f);

            // Restore font and color settings
            gr.setFont(oFont);
            gr.setColor(oCol);

            // Undo translation & rotation
            gr.translate(-position.getX(), -position.getY());
        }
    }
}
